// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

// WorldBuilderView.cpp : implementation of the CWorldBuilderView class
//
#include "stdafx.h"
#include "WorldBuilder.h"
#include "MainFrm.h"
#include "WorldBuilderView.h"
#include "WorldBuilderDoc.h"
#include "ActionManager.h"
#include "AnimationManager.h"
#include "Ray.h"
#include "Sphere.h"
#include "Line.h"
#include "Quad.h"
#include "ShapeNode.h"
#include "LightManager.h"
#include "Color.h"
#include "GlWndUtils.h"
#include "PythonManager.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")



// TODO add virtual destructors!!

//gx::Vec3 unproject( int x, int y, double z);


// TODO highlight the tile under the selected block

// DETECTING SINGLE CLICKS
// Most applications deal with single clicks in the simplest 
// possible way, interpreting a BUTTONDOWN message as a single 
// click. However, this doesn't suffice when we want to consider
// double click events as well, because in that case for each
// double click action we would be responding to both events
// (single click and double click). Something a bit more complex
// is needed. Unfortunately, MFC doesn't make our life any easier.
// In case of dbl. clk., MFC sends the following messages:
//   (1) BUTTONDOWN
//   (2) BUTTONUP
//   (3) DOUBLECLICK
//   (4) BUTTONUP
// The longest lapse between the two button up events is given by 
// ::GetDoubleClickTime() (milliseconds). We use this fact in order
// to differentiate singleclicks from double clicks. On (2) we 
// start a timer set to ::GetDoubleClickTime() milliseconds. 
// If (3) happens, we kill the timer and handle a double click
// event. If the timer expires, we kill it as well and handle a 
// single click event. A side effect of this approach is that single 
// clicks are kind of slow (::GetDoubleClickTime() is around 400 ms),
// but this// is the usual way to deal with this in MFC apps (see
// the Solution Explorer of VS for instance). Things are complicated 
// further by the fact that two consecutive clicks are not considered 
// a double click (i.e., (3) is not sent) if the mouse cursor moves 
// in between (even 1 pixel). In that case MFC sends the following 
// sequence of messages:
//   (1) BUTTONDOWN
//   (2) BUTTONUP
//   (3) BUTTONDOWN
//   (4) BUTTONUP
// We interpret this in the same way as VS: as a single click event.
// Another possibility is three consecutive clicks, the first one on 
// a different cursor position. In that case MFC sends:
//   (1) BUTTONDOWN
//   (2) BUTTONUP
//   (3) BUTTONDOWN
//   (4) BUTTONUP
//   (5) DOUBLECLICK
//   (6) BUTTONUP
// We interpret this in the same way as VS: as a single click in the 
// first cursor position followed by a double click on the second cursor 
// position To implement this convoluted logic easily we use a finite state 
// machine


// TODO "face hover" should have precedence over "hover on another block", 
// even if that other block is closer, because otherwise we can't act on
// a face that has an adjacent block (the opposites is always possible 
// through careful aiming)


// CWorldBuilderView

IMPLEMENT_DYNCREATE(CWorldBuilderView, CView)

BEGIN_MESSAGE_MAP(CWorldBuilderView, CView)
    ON_WM_CREATE()
    ON_WM_SIZE()
    ON_WM_ERASEBKGND()
    ON_WM_KEYDOWN()
    ON_WM_KEYUP()
    ON_WM_TIMER()
    ON_WM_CHAR()
    ON_WM_MOUSEMOVE()
    ON_WM_MOUSEWHEEL()
    ON_WM_LBUTTONUP()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONDBLCLK()
    ON_WM_MBUTTONUP()
    ON_WM_MBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_WM_RBUTTONDOWN()
    ON_WM_RBUTTONDBLCLK()
    ON_COMMAND(ID_EDIT_COPY, &CWorldBuilderView::OnEditCopy)
    ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, &CWorldBuilderView::OnUpdateEditCopy)
    ON_COMMAND(ID_EDIT_PASTE, &CWorldBuilderView::OnEditPaste)
    ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, &CWorldBuilderView::OnUpdateEditPaste)
    ON_COMMAND(ID_EDIT_UNDO, &CWorldBuilderView::OnEditUndo)
    ON_COMMAND(ID_EDIT_REDO, &CWorldBuilderView::OnEditRedo)
    ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CWorldBuilderView::OnUpdateEditUndo)
    ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CWorldBuilderView::OnUpdateEditRedo)
    ON_COMMAND(ID_EDIT_CUT, &CWorldBuilderView::OnEditCut)
    ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, &CWorldBuilderView::OnUpdateEditCut)
    ON_COMMAND(ID_BLOCK_DELETE, &CWorldBuilderView::OnBlockDelete)
    ON_COMMAND(ID_MANIP_CAM, &CWorldBuilderView::OnManipCam)
    ON_UPDATE_COMMAND_UI(ID_MANIP_CAM, &CWorldBuilderView::OnUpdateManipCam)
    ON_COMMAND(ID_MANIP_OBJ, &CWorldBuilderView::OnManipObj)
    ON_UPDATE_COMMAND_UI(ID_MANIP_OBJ, &CWorldBuilderView::OnUpdateManipObj)
    ON_COMMAND(ID_MANIP_TRANSL, &CWorldBuilderView::OnManipTransl)
    ON_UPDATE_COMMAND_UI(ID_MANIP_TRANSL, &CWorldBuilderView::OnUpdateManipTransl)
    ON_COMMAND(ID_MANIP_ROT, &CWorldBuilderView::OnManipRot)
    ON_UPDATE_COMMAND_UI(ID_MANIP_ROT, &CWorldBuilderView::OnUpdateManipRot)
    ON_COMMAND(ID_MANIP_SCALE, &CWorldBuilderView::OnManipScale)
    ON_UPDATE_COMMAND_UI(ID_MANIP_SCALE, &CWorldBuilderView::OnUpdateManipScale)
    ON_COMMAND(ID_MANIP_PIVOT, &CWorldBuilderView::OnManipPivot)
    ON_CBN_SELENDOK(ID_MANIP_PIVOT, &CWorldBuilderView::OnManipPivot)
    ON_UPDATE_COMMAND_UI(ID_MANIP_PIVOT, &CWorldBuilderView::OnUpdateManipPivot)
    ON_COMMAND(ID_MANIP_FRAME, &CWorldBuilderView::OnManipFrame)
    ON_CBN_SELENDOK(ID_MANIP_FRAME, &CWorldBuilderView::OnManipFrame)
    ON_UPDATE_COMMAND_UI(ID_MANIP_FRAME, &CWorldBuilderView::OnUpdateManipFrame)
    ON_CBN_CLOSEUP(ID_MANIP_FRAME, &CWorldBuilderView::OnManipComboCloseup)
    ON_CBN_CLOSEUP(ID_MANIP_PIVOT, &CWorldBuilderView::OnManipComboCloseup)
    ON_COMMAND(ID_VIEW_PERSPECTIVE, &CWorldBuilderView::OnViewPerspective)
    ON_UPDATE_COMMAND_UI(ID_VIEW_PERSPECTIVE, &CWorldBuilderView::OnUpdateViewPerspective)
END_MESSAGE_MAP()

// CWorldBuilderView construction/destruction

CWorldBuilderView::CWorldBuilderView()
: m_middleBtnDown(false), 
  m_leftBtnClickState(ClickCycleStates::IDLE),
  m_rightBtnClickState(ClickCycleStates::IDLE),
  m_leftBtnDown(false),
  m_draggable(NULL),
  m_cameraMat(gx::eye),
  m_bgColor(.25f, .15f, .25f)
{
    m_cameraMat.make_transl(0, 1., 2.);
}




CWorldBuilderView::~CWorldBuilderView()
{
    m_gizmoManager.setView(NULL);
}

BOOL CWorldBuilderView::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: Modify the Window class or styles here by modifying
    //  the CREATESTRUCT cs

    return __super::PreCreateWindow(cs);
}

void CWorldBuilderView::OnClose() 
{
    MakeCurrent();
    wglDeleteContext(m_hRC);
    CWnd::OnClose();
}

// CWorldBuilderView drawing

BOOL CWorldBuilderView::OnEraseBkgnd(CDC* /*pDC*/)
{
    return FALSE;
}

void CWorldBuilderView::drawGrid() const
{
    glPushAttrib(GL_ENABLE_BIT);
        
    glDisable(GL_LIGHTING);

        makeBrighter(m_bgColor)();

        double absCamY = fabs(m_cameraMat.translation.y);
        int N = std::min(
                    std::max(25, int(25 * absCamY)),
                    200);

        int camX = int(m_cameraMat.translation.x);
        int camZ = int(m_cameraMat.translation.z);

        glBegin(GL_LINES);
            for(int i = -N; i < N; ++i) 
            {
                glVertex3i(camX + i, 0, camZ-N);
                glVertex3i(camX + i, 0, camZ+N);
            }
            
            for(int i = -N; i < N; ++i) 
            {
                glVertex3i(camX + -N, 0, camZ+i);
                glVertex3i(camX + N, 0, camZ+i);
            }
        glEnd();

    glPopAttrib();
}

void drawScreenAxes(View3d * view, const gx::Mat3 & axes, 
    int w, int h, double brightness, double length, double thickness)
{
    // A point on the lower left corner of the screen

    gx::Vec3 p = view->unproject(
        ScreenPoint(int(w * .05), int(h * .95)), 0.5);
  
    double factor = view->computeConstantScaleFactor(p);

    glPushMatrix();

    glTranslated(p.x, p.y, p.z);
    
    double m[16];
    gx::Mat4().make_rot(axes).to_array(m);
    glMultMatrixd(m);

    double scale = length * factor;
    glScaled(scale, scale, scale);

    glPushAttrib(GL_LINE_BIT | GL_ENABLE_BIT);
        glLineWidth(float(thickness));
        glDisable(GL_LIGHTING);
        glDisable(GL_FOG);
        glDisable(GL_DEPTH_TEST);
        glBegin(GL_LINES);
            glColor3d(brightness, 0., 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(1., 0., 0.);
            
            glColor3d(0., brightness, 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 1., 0.);

            glColor3d(0., 0., brightness);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 0., 1.);
        glEnd();

    glPopAttrib();
    glPopMatrix();
}

void drawAxes(bool balls=false)
{
    glPushMatrix();
    glPushAttrib(GL_LINE_BIT | GL_ENABLE_BIT);
        glScaled(.2, .2, .2);
        glLineWidth(1.5f);
        glDisable(GL_LIGHTING);
        glBegin(GL_LINES);
            glColor3d(1., 0., 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(1., 0., 0.);
            
            glColor3d(0., 1., 0.);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 1., 0.);

            glColor3d(0., 0., 1.);
            glVertex3d(0., 0., 0.);
            glVertex3d(0., 0., 1.);
        glEnd();

        if(balls)
        {
            glPushAttrib(GL_POINT_BIT);
            glPointSize(10.f);
            glBegin(GL_POINTS);
                glColor3d(1., 0.5, 0.5);
                glVertex3d(1., 0., 0.);
        
                glColor3d(.5, 1., 0.5);
                glVertex3d(0., 1., 0.);

                glColor3d(.5, .5, 1.);
                glVertex3d(0., 0., 1.);
            glEnd();
            glPopAttrib();
        }
    glPopAttrib();
    glPopMatrix();
}


extern const GLubyte stippleMask[17][128];

void CWorldBuilderView::invalidateImpl()
{
    if((HWND)*this)
    {
        Invalidate();
    }
}

namespace
{

void glColor(float r, float g, float b, float a)
{   
    glColor4f(r, g, b, a);
}

} // anonymous namespace


void CWorldBuilderView::OnDraw(CDC* /*pDC*/)
{
    // We assume that we are drawing because something changing in the scene,
    // which means that the object under the mouse cursor could have changed
    // TODO find a more elegant and efficient way to do this
    // TODO put this inside a no-args overload of updateHover
    CPoint point;
    ::GetCursorPos(&point);
    ScreenToClient(&point);

    MakeCurrent();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // TODO extract this into method drawGL()

    setProjectionMatrix();

    setModelViewMatrix();
    
    m_world->getLightManager().updateLighting();

    drawGrid();

    int w, h;
    getDims(w, h);
    drawScreenAxes(this, gx::eye, w, h, 1., .2, 1.5);
    //if(SelectionManager::instance().isNodeSelected())
    //{
    //    drawScreenAxes(this,
    //        SelectionManager::instance().getSelectedNode()
    //            ->getGlobalTransform().rotation, 
    //        rc.Width(), rc.Height(),
    //        1., .25, 2.5);
    //}
    //drawAxes();

    Selectable* sel = m_world->getSelectionManager().getSelection();
    ShapeNode* selNode = dynamic_cast<ShapeNode*>(sel);
    Node* hoverNode = dynamic_cast<Node*>(m_hoverItem);
    Color yellow_(ColorIds::Yellow);
    if(selNode != NULL && selNode != hoverNode && selNode->isBounded())
    {
        makeDarker(makeDarker(yellow_))();
        selNode->drawAABB();
    }

    if(hoverNode && hoverNode->isBounded())
    {
        yellow_();
        hoverNode->drawAABB();
    }

    DrawableSet::iterator it = m_drawables.begin();
    for(; it != m_drawables.end(); ++it)
    {
        (*it)->draw(this);
    }

    SwapBuffers(GetDC()->m_hDC);
}


void CWorldBuilderView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
    CMenu mainMenu;
    VERIFY(mainMenu.LoadMenu(IDR_MAINFRAME));
    
    CMenu * blockMenu = mainMenu.GetSubMenu(3);
    ASSERT(blockMenu != NULL);

    theApp.GetContextMenuManager()->ShowPopupMenu(
        blockMenu->GetSafeHmenu(), point.x, point.y, this, TRUE);
}


// CWorldBuilderView diagnostics

#ifdef _DEBUG
void CWorldBuilderView::AssertValid() const
{
    __super::AssertValid();
}

void CWorldBuilderView::Dump(CDumpContext& dc) const
{
    __super::Dump(dc);
}

//CWorldBuilderDoc* CWorldBuilderView::GetDocument() const // non-debug version is inline
//{
//	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWorldBuilderDoc)));
//	return (CWorldBuilderDoc*)m_pDocument;
//}

#endif //_DEBUG



// CWorldBuilderView message handlers

int CWorldBuilderView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if(__super::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO do this somewhere else (probably ctor. of App)
    static bool glutInitialized = false;
    if(!glutInitialized) 
    {
        int argc = 0;
        char * argv[] = { "dummy" };
        glutInit(&argc, argv);
        glutInitialized = true;
    }

    bool ok = ::SetupPixelFormat(GetDC());
    ASSERT(ok);
    m_hRC = wglCreateContext(GetDC()->m_hDC);
    ASSERT(m_hRC);
    MakeCurrent();
    
    // TODO extract method "initGL"

    glEnable(GL_DEPTH_TEST);

    // We don't use blending at the moment but just in case...
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
    
    glClearColor(m_bgColor[0], m_bgColor[1], m_bgColor[2], m_bgColor[3]);

    glEnable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_NORMALIZE);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

    glEnable (GL_FOG);
    glFogf(GL_FOG_START, 10.f);
    glFogf(GL_FOG_END, 50.f);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogfv(GL_FOG_COLOR, m_bgColor.to_array());
    glHint(GL_FOG_HINT, GL_NICEST);

    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    glShadeModel(GL_SMOOTH);

    setColorCallback(&glColor);

    return 0;
}

void CWorldBuilderView::resize(int w, int h)
{
    glViewport(0, 0, w, h);
}

void CWorldBuilderView::OnSize(UINT nType, int cx, int cy)
{
    __super::OnSize(nType, cx, cy);

    MakeCurrent();
    resize(cx, (cy == 0) ? 1 : cy);
}


void CWorldBuilderView::MakeCurrent()
{
    wglMakeCurrent(GetDC()->m_hDC, m_hRC);
}


void CWorldBuilderView::cameraShift(double dX, double dY)
{
    gx::Vec3 offset = m_cameraMat * gx::Dir(gx::I * dX + gx::J * dY);
    m_cameraMat.translation += offset;
    invalidate();
}

void CWorldBuilderView::cameraDolly(double amount)
{
    if(m_projectionMode == ProjectionModes::Perspective)
    {
        gx::Vec3 offset = m_cameraMat * gx::Dir(gx::K * amount);
        m_cameraMat.translation += offset;
    }
    else
    {
        // In ortho mode we can't use eyeZ as a measure of Zoom because
        // then camera rotations cause annoying zoom in and outs
        m_orthoScale *= (1. + amount * .3);
    }
    invalidate();
}

// = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = -
// Implementation of View3d virtual members
// = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = -

gx::Vec3 CWorldBuilderView::unprojectImpl(const ScreenPoint & p, double z) const
{
    GLfloat wx, wy, wz;
    GLdouble cx, cy, cz;
    
    GLdouble mv[16];
    glGetDoublev( GL_MODELVIEW_MATRIX, mv );

    GLdouble proj[16];
    glGetDoublev( GL_PROJECTION_MATRIX, proj );

    GLint vp[4];
    glGetIntegerv( GL_VIEWPORT, vp );

    wx = (GLfloat)p.x;
    wy = (GLfloat)vp[3] - (float)p.y;
    wz = (GLfloat)z;
//	glReadPixels( x, (int)wy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &wz );

    gluUnProject( wx, wy, wz, mv, proj, vp, &cx, &cy, &cz );

    return gx::Vec3(cx, cy, cz);
}


ScreenPoint CWorldBuilderView::projectImpl(const gx::Vec3 & v) const
{
    CRect rc;
    GetClientRect(rc);
    int h = rc.Height();

    GLfloat wx, wy, wz;
    GLdouble cx, cy, cz;
    
    GLdouble mv[16];
    glGetDoublev( GL_MODELVIEW_MATRIX, mv );

    GLdouble proj[16];
    glGetDoublev( GL_PROJECTION_MATRIX, proj );

    GLint vp[4];
    glGetIntegerv( GL_VIEWPORT, vp );

    double a[3];
    gluProject(v.x, v.y, v.z, mv, proj, vp, a, a + 1, a + 2);
    ScreenPoint result((int)a[0], (int)a[1]);
    result.y = h - result.y;
    return result;
}

gx::Mat4 CWorldBuilderView::getCameraMatrixImpl() const
{
    return m_cameraMat;
}

void CWorldBuilderView::getDimsImpl(int & w, int & h) const
{
    CRect rc;
    GetClientRect(rc);
    w = rc.Width();
    h = rc.Height();
}





// = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = - = -



// ****************************************************************************
// ****************************************************************************
// DEALING WITH MOUSE MESSAGES

//	Dealing with the mouse wheel message

BOOL CWorldBuilderView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    // TODO make SHIFT + Mouse Wheel => Roll
    
    cameraDolly(-zDelta * 5e-3);
    return __super::OnMouseWheel(nFlags, zDelta, pt);
}

void CWorldBuilderView::onMButtonDrag(UINT /*iniFlags*/, CPoint iniPoint, 
    UINT currFlags, CPoint currPoint)
{
    int w, h;
    getDims(w, h);

    double dX = (currPoint.x - iniPoint.x) / (double)w;
    double dY = (currPoint.y - iniPoint.y) / (double)h;

    m_cameraMat = m_iniCam;

    // Middle button + shift => shift camera in the screen plane
    if((currFlags & MK_SHIFT) == MK_SHIFT)
    {
        cameraShift(5. * dX, -5. * dY);
    }
    else
    {
        // Yaw is rotation about screen vertical axis
        gx::Mat3 yawMat;
        yawMat.make_rot(gx::AxisAngle(-m_cameraMat.get_col(Dims::Y), dX));

        // Pitch is rotation about screen horizontal axis
        gx::Mat3 pitchMat;
        pitchMat.make_rot(gx::AxisAngle(-m_cameraMat.get_col(Dims::X), dY));
        
        // For n>2 product of rotation matrices is not commutative, but here
        // it doesn't seem to matter much (maybe because we have small rotations,
        // and the product of infinitesimal rotations commutes). Here we tilt and then 
        // swing but it could have been the other way around
        gx::Mat3 pitchYawMat = yawMat * pitchMat;

        // Middle button + ctrl => rotate camera about selected 
        // object (or about the origin), like 3D editors (Blender)
        if((currFlags & MK_CONTROL) == MK_CONTROL)
        {
            gx::Vec3 pivot = gx::zero;
            Selectable* selection = 
                m_world->getSelectionManager().getSelection();
            
            if(TransformNode* selNode = dynamic_cast<TransformNode*>(selection))
            {
                pivot = selNode->getGlobalTransform().translation;
            }

            // This is the way to rotate an object X about a pivot point P:
            // X = [T(P) * R * T(-P)] * X;
            m_cameraMat = gx::Mat4().make_pivot_rot(pivot, pitchYawMat) 
                         * m_cameraMat;
        }

        // Middle button only => camera tilt and swing (like a FPS)
        else
        {
            // TODO exctract this into a method like cameraDolly or shift

            // Intrinsic rotation (about object's own origin)
            // This is the way to do it using pivot rotation (M is the camera matrix)
            // M = (gx::Mat4(M.translation) * swingTiltMat * gx::Mat4(-M.translation)) * M;
            // But [gx::Mat4(-M.translation)) * M] equals M.rotation, and 
            // [gx::Mat4(M.translation) * swingTiltMat * M.rotation] equals 
            // gx::Mat4(swingTiltMat * M.rotation, M.translation)], so we can simply write:
            
            m_cameraMat.rotation = pitchYawMat * m_cameraMat.rotation;
        }
        invalidate();
    }
    // TODO maybe make this shift + rght button
    // Camera cameraDolly
    //else if(m_middleBtnDown)
    //{
    //	m_cameraMat.translation = m_iniCamPos;
    //	cameraDolly(5. * dY);
    //}
}

//	Dealing with the mouse move message

void CWorldBuilderView::OnMouseMove(UINT nFlags, CPoint point)
{
    // For some reason this is not always the case
    bool mouseActuallyMoved = !(point == m_point);
    
    if(m_leftBtnDown && mouseActuallyMoved)
    {
        onLButtonDrag(m_flags, m_point, nFlags, point);
    }
    else if(m_middleBtnDown && mouseActuallyMoved) 
    {
        onMButtonDrag(m_flags, m_point, nFlags, point);
    }
    else
    {
        // Don't change hover during a drag op.
        int subName;
        Pickable* item = doPickTest(point, subName);
        bool hoverChanged = m_hoverItem != item 
                       || m_hoverItemPart != subName;
        m_hoverItemPart = subName;
        m_hoverItem = item;

        if(hoverChanged)
        {
            invalidate();
        }

    }
    __super::OnMouseMove(nFlags, point);
}


void CWorldBuilderView::onLButtonDrag(UINT iniFlags, CPoint iniPoint,
    UINT /*currFlags*/, CPoint currPoint)
{
    //dbg("DR");
    // If the mouse has moved between down and up it's not a 
    // single click. We must set this variable to false or the
    // single click timer will be activated when the left button
    // up message is received
    m_singleClickPossible = false;

    // Dragging means that left button is down and the mouse 
    // has moved at least a pixel

    bool firstDragEvent = m_leftBtnClickState != ClickCycleStates::DRAGGING;
    //m_dragging = true;

    Draggable* draggable = dynamic_cast<Draggable*>(m_hoverItem);
    if(draggable)
    {
        m_leftBtnClickState = ClickCycleStates::DRAGGING;
        if(firstDragEvent)
        {
            m_draggable = draggable;
            draggable->onBeginDrag(iniPoint, 
                                   m_hoverItemPart);
        }
        else
        {
            ASSERT(m_draggable);
            m_draggable->onDrag(currPoint);
            invalidate();
        }
    }
}

// ****************************************************************************
// Dealing with the left mouse button

void CWorldBuilderView::OnLButtonDown(UINT nFlags, CPoint point)
{
    m_leftBtnDown = true;
    SetCapture();

    m_flags = nFlags; // We're gonna need those if this turns 
    m_point = point;  // out to be a single click

    bool selectionChanged = false;
    if(m_hoverItem && m_hoverItem->isSelectable())
    {
        selectionChanged = m_world->getSelectionManager().select(m_hoverItem);
    }
    
    if(m_leftBtnClickState != ClickCycleStates::IDLE)
    {
        // Double click but the mouse moved slightly

        ASSERT(m_leftBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        if(m_singleClickPossible)
        {
            KillTimer(TimerIDs::LBTN_DBL_CLK);
            onLSingleClick(nFlags, point);
        }
    }

    m_singleClickPossible = !selectionChanged; // Timer won't be started
    m_leftBtnClickState = ClickCycleStates::DOWN_RCVD;

    __super::OnLButtonDown(nFlags, point);
}

void CWorldBuilderView::OnLButtonUp(UINT nFlags, CPoint point)
{
    m_leftBtnDown = false;
    ReleaseCapture();

    // TODO comment this
    if(m_leftBtnClickState == ClickCycleStates::DRAGGING)
    {
        Selectable* selection = m_world->getSelectionManager().getSelection();
        Node* selNode = dynamic_cast<Node*>(selection);
        //Node* hoverNode = HoverManager::instance().getSelectedNode();
        //if(selNode != NULL && hoverNode == NULL)
        //{
            //m_draggedBlock
                //AnimationManager::instance().performAnimatedInsertBlockAction(
                //		destBlock);
            //ActionManager::instance().performAction(ref<Action>(
            //	new InsertBlockAction(m_draggedBlock)));
        //}
        ASSERT(m_draggable != NULL); // Otherwise the drag cycle wouldn't have started
        m_draggable->onEndDrag();
        invalidate();
        m_leftBtnClickState = ClickCycleStates::IDLE;
    }
    // if m_doubleClickTimerRunning_L is false, this is the first time we 
    // receive this message in the current cycle. So we start the timer which
    // will allow us to determine whether this is double click or single click
    else if(m_leftBtnClickState == ClickCycleStates::DOWN_RCVD)
    {
        m_leftBtnClickState = ClickCycleStates::FIRST_UP_RCVD;
        if(m_singleClickPossible)
        {
            SetTimer(TimerIDs::LBTN_DBL_CLK, ::GetDoubleClickTime(), NULL);
        }
    }
    // Otherwise it's the second time we receive this message, which means that
    // this is part of a double click process. Simply assert that the timer
    // has been correctly killed inside the double click handler
    else if(m_leftBtnClickState == ClickCycleStates::DBL_CLCK_RCVD)
    {
        KillTimer(TimerIDs::LBTN_DBL_CLK);
        m_leftBtnClickState = ClickCycleStates::IDLE;
    }
    __super::OnLButtonUp(nFlags, point);
}

void CWorldBuilderView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // If we receive this message we are sure we have a double click,
    // so we don't need to keep the single-click detection timer running
    ASSERT(m_leftBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
    m_leftBtnClickState = ClickCycleStates::DBL_CLCK_RCVD;

    // Call the appropriate handler
    onLDoubleClick(nFlags, point);

    __super::OnLButtonDblClk(nFlags, point);
}

// Not really a mouse handler but that's what we use if for
void CWorldBuilderView::OnTimer(UINT_PTR nIDEvent)
{
    if(nIDEvent == TimerIDs::LBTN_DBL_CLK)
    {
        KillTimer(nIDEvent);
        ASSERT(m_leftBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        m_leftBtnClickState = ClickCycleStates::IDLE;
        ASSERT(m_singleClickPossible);
        onLSingleClick(m_flags, m_point);
    }
    else if(nIDEvent == TimerIDs::RBTN_DBL_CLK)
    {
        KillTimer(nIDEvent);
        ASSERT(m_rightBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        m_rightBtnClickState = ClickCycleStates::IDLE;
        onRSingleClick(m_flags, m_point);
    }

    __super::OnTimer(nIDEvent);
}

//Node translateBlockCopy(const Node & block, int axis)
//{
//	Node result = block;
//
//	switch(axis)
//	{
//	case 1:
//	case -1:
//		result.tile.x += axis;
//		break;
//	case 2:
//	case -2:
//		result.height += axis / 2;
//		break;
//	case 3:
//	case -3:
//		result.tile.y += axis / 3;
//		break;
//	default:
//		assert(false);
//	}
//
//	return result;
//}

void CWorldBuilderView::onLSingleClick(UINT /*nFlags*/, CPoint /*point*/)
{
    //if(SelectionManager::instance().isNodeSelected())
    //{
    //	Node selectedBlock = SelectionManager::instance().getSelectedNode();

    //	double dummy;
    //	int clickedFace = selectedBlock.findFaceIntersection(
    //		computePickRay(m_point), dummy);
    //	ASSERT(clickedFace != 0);

    //	Node newBlock = translateBlockCopy(selectedBlock, clickedFace);
    //	gx::Vec3 delta;
    //	delta.x = newBlock.tile.x - selectedBlock.tile.x;
    //	delta.y = newBlock.height - selectedBlock.height;
    //	delta.z = newBlock.tile.y - selectedBlock.tile.y;

    //	ref<Shape> finalShape = NodeManager::instance().getShape(selectedBlock)->clone();
    //	finalShape->setPosition(finalShape->getPosition() + delta);
    //	
    //	AnimationManager::instance().startBlockAnimation(selectedBlock, ref<Shape>(), finalShape, 200., 
    //		ref<Action>(new MoveNodeAction(selectedBlock, newBlock)));
    //}
}

void CWorldBuilderView::onLDoubleClick(UINT nFlags, CPoint point)
{
    Selectable* selection = m_world->getSelectionManager().getSelection();
    ShapeNode* selNode = dynamic_cast<ShapeNode*>(selection);
    if(selNode != NULL)
    {
        // Could use m_hoverFace here as well because dbl-click is very fast
        onBlockLDoubleClick(selNode, nFlags, point);
    }
    //else if(SelectionManager::instance().isTileSelected())
    //{
    //	onTileLDoubleClick(SelectionManager::instance().getSelectedTile(), nFlags, point);
    //}
}

void CWorldBuilderView::onBlockLDoubleClick(
        Node* clickedBlock,
        UINT nFlags, CPoint /*point*/)
{
    //// If ctrl is pressed => change shape geometry
    //if((nFlags & MK_CONTROL) == MK_CONTROL)
    //{
    //	// TODO the name "cloneNextType" is horrible
    //	ref<const Shape> oldShape = NodeManager::instance().getShape(clickedBlock);
    //	ref<Shape> newShape = oldShape->cloneNextType();
    //	
    //	ActionManager::instance().performAction(ref<Action>(
    //		new ReplaceShapeAction(clickedBlock, newShape)));

    //}
    //// If ctrl is NOT pressed => create a new copy of the selected shape 
    //// in the direction dictated by the selected face
    //else
    //{
    //	Node newBlock = clickedBlock.extend(clickedFace);
    //	ref<Shape> newShape = 
    //		NodeManager::instance().getShape(clickedBlock)->clone();
    //	AnimationManager::instance().performAnimatedInsertBlockAction(
    //		newBlock, newShape);
    //}
    //invalidate();
}

void CWorldBuilderView::onTileLDoubleClick(
        const Tile & clickedTile, UINT nFlags, CPoint /*point*/)
{
    //// If ctrl is pressed, do nothing
    //// TODO create a random shape geometry
    //if((nFlags & MK_CONTROL) == MK_CONTROL)
    //{
    //}
    //// If ctrl is NOT pressed, create a new block at this tile
    //else
    //{
    //	Node newBlock(clickedTile, 0);
    //	ref<Shape> newShape(new CylinderShape);
    //	AnimationManager::instance()
    //		.performAnimatedInsertBlockAction(newBlock, newShape);
    //}
    //invalidate();
}

// ****************************************************************************


// ****************************************************************************
//	Dealing with the middle mouse button

void CWorldBuilderView::OnMButtonUp(UINT nFlags, CPoint point)
{
    m_middleBtnDown = false;
    ReleaseCapture();
    __super::OnMButtonUp(nFlags, point);
}

void CWorldBuilderView::OnMButtonDown(UINT nFlags, CPoint point)
{
    m_middleBtnDown = true;
    m_point = point;
    m_iniCam = m_cameraMat;
    SetCapture();
    __super::OnMButtonDown(nFlags, point);
}

// ****************************************************************************


// ****************************************************************************
//	Dealing with the right mouse button
// ****************************************************************************

void CWorldBuilderView::OnRButtonDown(UINT nFlags, CPoint point)
{
/*
    m_flags = nFlags; // We're gonna need those if this turns 
    m_point = point;  // out to be a single click

    bool selectionChanged = 
        SelectionManager::instance().updateFrom(HoverManager::instance());

    if(m_rightBtnClickState != ClickCycleStates::IDLE)
    {
        ASSERT(m_rightBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
        if(m_singleClickPossible)
        {
            KillTimer(TimerIDs::RBTN_DBL_CLK);
            onRSingleClick(nFlags, point);
        }
    }

    m_singleClickPossible = !selectionChanged;
    m_rightBtnClickState = ClickCycleStates::DOWN_RCVD;
*/
    __super::OnRButtonDown(nFlags, point);
}

void CWorldBuilderView::OnRButtonUp(UINT nFlags, CPoint point)
{
    // if m_doubleClickTimerRunning_L is false, this is the first time we 
    // receive this message in the current cycle. So we start the timer which
    // will allow us to determine whether this is double click or single click
    if(m_rightBtnClickState == ClickCycleStates::DOWN_RCVD)
    {
        m_rightBtnClickState = ClickCycleStates::FIRST_UP_RCVD;
        if(m_singleClickPossible)
        {
            SetTimer(TimerIDs::RBTN_DBL_CLK, ::GetDoubleClickTime(), NULL);
        }
    }
    // Otherwise it's the second time we receive this message, which means that
    // this is part of a double click process. Simply assert that the timer
    // has been correctly killed inside the double click handler
    else if(m_rightBtnClickState == ClickCycleStates::DBL_CLCK_RCVD)
    {
        KillTimer(TimerIDs::RBTN_DBL_CLK);
        m_rightBtnClickState = ClickCycleStates::IDLE;
    }
    else
    {
        ASSERT(m_rightBtnClickState == ClickCycleStates::IDLE); 
    }

    __super::OnRButtonUp(nFlags, point);
}

void CWorldBuilderView::OnRButtonDblClk(UINT nFlags, CPoint point)
{
    // If we receive this message we are sure we have a double click,
    // so we don't need to keep the single-click detection timer running
    ASSERT(m_rightBtnClickState == ClickCycleStates::FIRST_UP_RCVD);
    m_rightBtnClickState = ClickCycleStates::DBL_CLCK_RCVD;

    // Call the appropriate handler
    onRDoubleClick(nFlags, point);

    __super::OnRButtonDblClk(nFlags, point);
}

void CWorldBuilderView::onRSingleClick(UINT /*nFlags*/, CPoint point)
{
    // TODO do something about the context menu
    ClientToScreen(&point);
    OnContextMenu(this, point);
}

void CWorldBuilderView::onRDoubleClick(UINT nFlags, CPoint point)
{
//	if(SelectionManager::instance().isNodeSelected())
//	{
//		onBlockRDoubleClick(SelectionManager::instance().getSelectedNode(), 
//			HoverManager::instance().getSelectedFace(), nFlags, point);
//	}
//	else if(SelectionManager::instance().isTileSelected())
//	{
//		onTileRDoubleClick(SelectionManager::instance().getSelectedTile(), nFlags, point);
//	}
}

void CWorldBuilderView::onBlockRDoubleClick(const Node & clickedBlock, 
    int clickedFace, UINT nFlags, CPoint /*point*/)
{
    //ref<Shape> newShape = 
    //	NodeManager::instance().getShape(clickedBlock)->clone();

    //// If ctrl is pressed => scale selected shape in the direction dictated 
    //// by the selected face
    //if((nFlags & MK_CONTROL) == MK_CONTROL)
    //{
    //	newShape->scale(clickedFace);
    //}
    //// If ctrl is NOT pressed => rotate selected shape about the axis
    //// dictated by the selected face
    //else
    //{
    //	newShape->rotate(clickedFace);
    //}

    //AnimationManager::instance().startBlockAnimation(
    //	clickedBlock, ref<Shape>(), newShape, 300.,
    //	ref<Action>(new ReplaceShapeAction(clickedBlock, newShape)));
}

void CWorldBuilderView::onTileRDoubleClick(
        const Tile & /*clickedTile*/, UINT nFlags, CPoint /*point*/)
{
    // If ctrl is pressed, do nothing
    if((nFlags & MK_CONTROL) == MK_CONTROL)
    {
    }
    // If ctrl is NOT pressed, do nothing
    else
    {
    }
    //invalidate();
}

// ****************************************************************************

// ****************************************************************************
// ****************************************************************************

void CWorldBuilderView::update(double elapsedMicroSecs)
{
    bool ctrlDown = ::GetKeyState(VK_CONTROL) < 0;

    if(m_keyDown[VK_LEFT] || m_keyDown[VK_RIGHT])
    {
        if(ctrlDown)
        {
            // radians per microsecond
            const double YAW_SPEED_US = TWO_PI * SECS_IN_US / 2.; 
            double yawDelta = YAW_SPEED_US * elapsedMicroSecs;
            yawDelta = m_keyDown[VK_LEFT] ? yawDelta : -yawDelta;

            gx::Vec3 camY = m_cameraMat.get_col(Dims::Y);
            gx::Mat3 yawMat = gx::Mat3().make_rot(gx::AxisAngle(camY, yawDelta));
            m_cameraMat.rotation = yawMat * m_cameraMat.rotation;
            invalidate();
        }
        else
        {
            if(m_keyDown[VK_LEFT])
            {
                cameraShift(-STEPPING_SPEED * elapsedMicroSecs, 0.);
            }
            else if(m_keyDown[VK_RIGHT])
            {
                cameraShift(STEPPING_SPEED * elapsedMicroSecs, 0.);	
            }
        }
    }

    if(m_keyDown[VK_UP] || m_keyDown[VK_DOWN]) 
    {
        if(ctrlDown)
        {
            const double PITCH_SPEED_US = TWO_PI * SECS_IN_US / 2.; 
            double pitchDelta = PITCH_SPEED_US * elapsedMicroSecs;
            pitchDelta = m_keyDown[VK_UP] ? pitchDelta : -pitchDelta;

            gx::Vec3 camX = m_cameraMat.get_col(Dims::X);
            gx::Mat3 pitchMat = gx::Mat3().make_rot(gx::AxisAngle(camX, pitchDelta));
            m_cameraMat.rotation = pitchMat * m_cameraMat.rotation;

            invalidate();
        }
        else
        {
            if(m_keyDown[VK_UP])
            {
                cameraDolly(-STEPPING_SPEED * elapsedMicroSecs);
            }
            else if(m_keyDown[VK_DOWN])
            {
                cameraDolly(STEPPING_SPEED * elapsedMicroSecs);
            }
        }
    }

    if(m_keyDown[VK_PRIOR] || m_keyDown[VK_NEXT])
    {
        if(ctrlDown)
        {
            // radians per microsecond
            const double ROLL_SPEED_US = TWO_PI * SECS_IN_US / 2.; 
            double rollDelta = ROLL_SPEED_US * elapsedMicroSecs;
            rollDelta = m_keyDown[VK_PRIOR] ? rollDelta : -rollDelta;

            gx::Vec3 camZ = m_cameraMat.get_col(Dims::Z);
            gx::Mat3 rollMat = gx::Mat3().make_rot(gx::AxisAngle(camZ, rollDelta));
            m_cameraMat.rotation = rollMat * m_cameraMat.rotation;
            invalidate();
        }
        else
        {
            if(m_keyDown[VK_PRIOR])
            {
                cameraShift(0., STEPPING_SPEED * elapsedMicroSecs);
            }
            else if(m_keyDown[VK_NEXT])
            {
                cameraShift(0., -STEPPING_SPEED * elapsedMicroSecs);
            }
        }
    }






    //if(AnimationManager::instance().performAnimationStep(elapsedMicroSecs / 1000.))
    //{
    //	invalidate();
    //}
}

void CWorldBuilderView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    m_keyDown[nChar] = true;
    __super::OnKeyDown(nChar, nRepCnt, nFlags);
}
 
void CWorldBuilderView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    m_keyDown[nChar] = false;	
    __super::OnKeyUp(nChar, nRepCnt, nFlags);
}


const GLubyte stippleMask[17][128] =
{
  /* NOTE: 0% opaqueness is faster to set and probably faster to render with:
    glDisable(GL_POLYGON_STIPPLE);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); */
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},

  {0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},

  {0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x44, 0x44, 0x44, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x11, 0x11, 0x11, 0x11},

  {0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55},

  {0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55},

  {0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xee, 0xee, 0xee, 0xee, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xbb, 0xbb, 0xbb, 0xbb, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55},

  {0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xdd, 0xdd, 0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77},

  {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0x77, 0x77, 0x77},

  /* NOTE: 100% opaqueness is faster to set and probably faster to render with:
        glDisable(GL_POLYGON_STIPPLE); */
  {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};

// ****************************************************************************
// Implementation of the functionality in the Edit menu
// ****************************************************************************

// TODO this should't be here
CString formatName(_T("WorldBuilderBlock"));

// IMPORTANT: OLE must be initialize to use the clipboard functionality
// See call to AfxOLEInit() in CAppClass::InitInstace()
// If we don't initialize OLE, trying to use the clipboard functions fails 

void CWorldBuilderView::OnEditCopy()
{
    /*
    // Copy selected block to clipboard

    // 1. Create a new COleDataSource.
    // 2. Create a new CSharedFile (see initialization in example below).
    // 3. Write info to the CSharedFile instance.
    // 4. Detach the memory associated with the CSharedFile.
    // 5. Pass the detached memory and format info to CacheGlobalData().
    // 6. Place the data on the clipboard using SetClipboard().

    // ************************************************************************
    // Remove the current Clipboard contents. This involve opening it,
    // emptying it, and closing it again. Closing it is very important.
    // Otherwise, the call to SetClipboard below fails miserably beacuse 
    // it can't grab the clipboard if it's already opened by us)

    if(!OpenClipboard())
    {
        AfxMessageBox(_T("Cannot open the Clipboard"));
        return;
    }
    if(!EmptyClipboard())
    {
        AfxMessageBox(_T("Cannot empty the Clipboard"));
        return;
    }
    CloseClipboard();
    
    // ************************************************************************

    // ************************************************************************
    // Serialize To Clipboard

    CSharedFile sf(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT);
    CArchive ar(&sf, CArchive::store);

    // This would be the "standard" way to do it, but the way our Shape
    // hierarchy is designed, it doesn't play with the Serialization mechanism
    // (see how the function that reads a shape from a CArchive has to be static)
    // obj->Serialize(ar); 

    std::ostringstream oss;
    ShapeFactory::instance().write(
        *NodeManager::instance().getShape(
            SelectionManager::instance().getSelectedNode()), oss);
    writeTextToArchive(oss.str(), ar);

    ar.Close();

    HGLOBAL hMem = sf.Detach();
    if(!hMem) 
    {
        AfxMessageBox(_T("Unexpected error"));
        return;
    }

    UINT format = RegisterClipboardFormat(formatName);
    if(format == 0)
    {
        AfxMessageBox(_T("Cannot register Clipboard format"));
        return;
    }

    COleDataSource * pSource = new COleDataSource;
    pSource->CacheGlobalData((CLIPFORMAT)format, hMem);
    pSource->SetClipboard();
    */
    // ************************************************************************
}

void CWorldBuilderView::OnUpdateEditCopy(CCmdUI *pCmdUI)
{
    // Enable the "copy" command only if there's a block selected
    ShapeNode* selNode = 
        dynamic_cast<ShapeNode*>(m_world->getSelectionManager().getSelection());
    pCmdUI->Enable(selNode != NULL);
}

void CWorldBuilderView::OnEditPaste()
{
    /*
    // Copy block from clipboard to selected tile

    // TODO paste should work also when a face is selected!!

    // 1. Create a COleDataObject instance.
    // 2. Check to see if the format we want (Node) is available.
    // 3. Place the data associated with the clipboard into a CMemFile.
    // 4. Read the data out of the CMemFile.
    // 5. Release the global memory.

    COleDataObject obj;
    if (obj.AttachClipboard()) 
    {
        UINT format = RegisterClipboardFormat(formatName);
        if (obj.IsDataAvailable((CLIPFORMAT)format)) 
        {
            HGLOBAL hmem = obj.GetGlobalData((CLIPFORMAT)format);
            CMemFile sf((BYTE*) ::GlobalLock(hmem), ::GlobalSize(hmem));

            CArchive ar(&sf, CArchive::load);

            std::string wholeText = readCompleteTextArchive(ar);
            std::istringstream iss(wholeText);
            ref<Shape> shape = ShapeFactory::instance().read(iss);

            ar.Close();

            ::GlobalUnlock(hmem);

            AnimationManager::instance().performAnimatedInsertBlockAction(
                    Node(SelectionManager::instance().getSelectedTile(), 0), shape);
        }
    }*/
}

void CWorldBuilderView::OnUpdateEditPaste(CCmdUI *pCmdUI)
{
    /*bool shouldEnable = false;

    // Only enable "paste" command if a tile is selected and...
    if(SelectionManager::instance().isTileSelected())
    {
        // ...if a block object is currently in the clipboard
        COleDataObject obj;
        if (obj.AttachClipboard()) 
        {
            UINT format = RegisterClipboardFormat(formatName);
            shouldEnable = !!obj.IsDataAvailable((CLIPFORMAT)format);
        }
    }
    pCmdUI->Enable(shouldEnable);*/

}

void CWorldBuilderView::OnEditUndo()
{
    ActionManager::instance().undo(); 	// The manager takes care of everything
}

void CWorldBuilderView::OnUpdateEditUndo(CCmdUI *pCmdUI)
{
    // Ask the doc if undo is possible
    pCmdUI->Enable(ActionManager::instance().canUndo());
}

void CWorldBuilderView::OnEditRedo()
{
    ActionManager::instance().redo(); 	// The manager takes care of everything
}

void CWorldBuilderView::OnUpdateEditRedo(CCmdUI *pCmdUI)
{
    // Ask the doc if redo is possible
    pCmdUI->Enable(ActionManager::instance().canRedo());
}

void CWorldBuilderView::OnEditCut()
{
    // Cup = copy + delete
    //OnEditCopy();
    //AnimationManager::instance().performAnimatedEraseBlockAction(
    //		SelectionManager::instance().getSelectedNode());
}

void CWorldBuilderView::OnUpdateEditCut(CCmdUI *pCmdUI)
{
    // Cut is enabled if copy is enabled
    OnUpdateEditCopy(pCmdUI);
}

// ****************************************************************************

void CWorldBuilderView::selectionChangedImpl(SelectionManager* sender)
{
    updatePivotCombo();
    updateFrameCombo();
    
    View3d::selectionChangedImpl(sender);
}

// ****************************************************************************

void CWorldBuilderView::OnBlockDelete()
{
    //AnimationManager::instance().performAnimatedEraseBlockAction(
    //	SelectionManager::instance().getSelectedNode());
}

// :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :

// :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :

void CWorldBuilderView::onManipId(GizmoAxesType id)
{
    m_gizmoManager.setAxesType(id);
}

void CWorldBuilderView::onUpdateManipId(CCmdUI *pCmdUI, GizmoAxesType id)
{
    pCmdUI->SetCheck(m_gizmoManager.getAxesType() == id);
}

void CWorldBuilderView::OnManipCam()
{
    onManipId(GizmoAxesTypes::VIEW);
}

void CWorldBuilderView::OnUpdateManipCam(CCmdUI *pCmdUI)
{
    onUpdateManipId(pCmdUI, GizmoAxesTypes::VIEW);
}

void CWorldBuilderView::OnManipObj()
{
    onManipId(GizmoAxesTypes::NODE);
}

void CWorldBuilderView::OnUpdateManipObj(CCmdUI *pCmdUI)
{
    onUpdateManipId(pCmdUI, GizmoAxesTypes::NODE);
}

// :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :

void CWorldBuilderView::updatePivotCombo()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipPivotComboBtn();

    btn->RemoveAllItems();

    GizmoPivotType pivotId = m_gizmoManager.getPivotType();
    Node* pivot = NULL;
    if(pivotId == GizmoPivotTypes::OTHER_NODE)
    {
        pivot = m_gizmoManager.getPivotNode();
        if(pivot != NULL)
        {
            std::string name = pivot->getName();
            CString str(name.empty() ? "(unnamed)" : name.c_str());
            btn->AddItem(str, (DWORD)-1);
        }
    }
    
    btn->AddItem(L"Node origin", GizmoPivotTypes::NODE);
    btn->AddItem(L"World origin", GizmoPivotTypes::WORLD);
    btn->AddItem(L"Parent origin", GizmoPivotTypes::PARENT);

    Selectable* selection = m_world->getSelectionManager().getSelection();
    Node* selNode = dynamic_cast<Node*>(selection);
    if(selNode != NULL)
    {
        if(selNode != pivot)
        {
            btn->AddItem(L"Current selection", GizmoPivotTypes::OTHER_NODE);
        }
    }

    if(pivotId == GizmoPivotTypes::OTHER_NODE)
    {
        btn->SelectItem(0);
    }
    else
    {
        btn->SelectItem((int)pivotId);
    }
}

void CWorldBuilderView::OnManipPivot()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipPivotComboBtn();
    int sel = btn->GetCurSel();
    DWORD data = btn->GetItemData(sel);
    if(data >= GizmoPivotTypes::NODE && data <= GizmoPivotTypes::PARENT)
    {
        m_gizmoManager.setPivotNode(GizmoPivotType(data));
    }
    else
    {
        if(data == GizmoPivotTypes::OTHER_NODE)
        {
            Selectable* selection = m_world->getSelectionManager().getSelection();
            Node* selNode = dynamic_cast<Node*>(selection);
            ASSERT(selNode != NULL);
            m_gizmoManager.setPivotNode(selNode);
        }
        else
        {
            // Node name (first item) selected: do nothing
            ASSERT(data == -1);
            ASSERT(m_gizmoManager.getPivotType() == GizmoPivotTypes::OTHER_NODE);
        }
    }
    // Update the combo because the node name (first item)
    // might have to be removed
    updatePivotCombo();
}


void CWorldBuilderView::OnUpdateManipPivot(CCmdUI *pCmdUI)
{
    // Initial update
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipPivotComboBtn();
    if(btn->GetCount() == 0)
    {
        updatePivotCombo();
    }
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void CWorldBuilderView::updateFrameCombo()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipFrameComboBtn();

    btn->RemoveAllItems();

    GizmoAxesType frameId = m_gizmoManager.getAxesType();
    Node* frameNode = NULL;
    if(frameId == GizmoAxesTypes::OTHER_NODE)
    {
        frameNode = m_gizmoManager.getAxesNode();
        if(frameNode != NULL)
        {
            std::string name = frameNode->getName();
            CString str(name.empty() ? "(unnamed)" : name.c_str());
            btn->AddItem(str, (DWORD)-1);
        }
    }
    
    btn->AddItem(L"Node frame", GizmoAxesTypes::NODE);
    btn->AddItem(L"World frame", GizmoAxesTypes::WORLD);
    btn->AddItem(L"Parent frame", GizmoAxesTypes::PARENT);
    btn->AddItem(L"View frame", GizmoAxesTypes::VIEW);

    Selectable* selection = m_world->getSelectionManager().getSelection();
    Node* selNode = dynamic_cast<Node*>(selection);
    if(selNode != NULL)
    {
        if(selNode != frameNode)
        {
            btn->AddItem(L"Current selection", GizmoAxesTypes::OTHER_NODE);
        }
    }

    if(frameId == GizmoAxesTypes::OTHER_NODE)
    {
        btn->SelectItem(0);
    }
    else
    {
        btn->SelectItem((int)frameId);
    }
}

void CWorldBuilderView::OnManipFrame()
{
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipFrameComboBtn();
    int sel = btn->GetCurSel();
    DWORD data = btn->GetItemData(sel);
    if(data >= GizmoAxesTypes::NODE && data <= GizmoAxesTypes::VIEW)
    {
        m_gizmoManager.setAxesType(GizmoAxesType(data));
    }
    else
    {
        if(data == GizmoAxesTypes::OTHER_NODE)
        {
            Selectable* selection = m_world->getSelectionManager().getSelection();
            Node* selNode = dynamic_cast<Node*>(selection);
            ASSERT(selNode != NULL);
            m_gizmoManager.setAxesNode(selNode);
        }
        else
        {
            // Node name (first item) selected: do nothing
            ASSERT(data == -1);
            ASSERT(m_gizmoManager.getAxesType() == GizmoAxesTypes::OTHER_NODE);
        }
    }
    // Update the combo because the node name (first item)
    // might have to be removed
    updateFrameCombo();
}

void CWorldBuilderView::OnUpdateManipFrame(CCmdUI *pCmdUI)
{
    // Initial update
    CMFCToolBarComboBoxButton * btn = 
        theApp.getMainFrame()->getManipFrameComboBtn();
    if(btn->GetCount() == 0)
    {
        updateFrameCombo();
    }
}

void CWorldBuilderView::OnManipComboCloseup()
{
    SetFocus();
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void CWorldBuilderView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    // --------------------------------------------------------------
    // We use the r, t and s keys to change the manipulator
    // --------------------------------------------------------------

    if(nChar == 's')
    {
        m_gizmoManager.setGizmoType(GizmoTypes::SCALE);
        return;
    }
    if(nChar == 'r')
    {
        m_gizmoManager.setGizmoType(GizmoTypes::ROTATE);
        return;
    }
    if(nChar == 't')
    {
        m_gizmoManager.setGizmoType(GizmoTypes::TRANSLATE);
        return;
    }

    // --------------------------------------------------------------
    // We use the p/P keys to change the pivot
    // --------------------------------------------------------------

    if(nChar == 'p')
    {
        Selectable* selection = m_world->getSelectionManager().getSelection();
        Node* selNode = dynamic_cast<Node*>(selection);
        if(selNode != NULL)
        {
            m_gizmoManager.setPivotNode(selNode);
            updatePivotCombo();
        }
    }
    
    // Uppercase p => cycle through the standard "node" and "global" pivots
    else if(nChar == 'P')
    {
        m_gizmoManager.cyclePivot();
        updatePivotCombo();
    }

    __super::OnChar(nChar, nRepCnt, nFlags);
}

void CWorldBuilderView::OnManipTransl()
{
    m_gizmoManager.setGizmoType(GizmoTypes::TRANSLATE);
}

void CWorldBuilderView::OnUpdateManipTransl(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(m_gizmoManager.getGizmoType() == GizmoTypes::TRANSLATE);
}

void CWorldBuilderView::OnManipRot()
{
    m_gizmoManager.setGizmoType(GizmoTypes::ROTATE);
}

void CWorldBuilderView::OnUpdateManipRot(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(m_gizmoManager.getGizmoType() ==  GizmoTypes::ROTATE);
}

void CWorldBuilderView::OnManipScale()
{
    m_gizmoManager.setGizmoType(GizmoTypes::SCALE);
}

void CWorldBuilderView::OnUpdateManipScale(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(m_gizmoManager.getGizmoType() ==  GizmoTypes::SCALE);
}

void CWorldBuilderView::onWorldChanged(World* newWorld)
{
    m_gizmoManager.setView(this);

    NodeManager& nm = newWorld->getNodeManager();
    for(NodeIterator it = nm.nodesBegin(); it != nm.nodesEnd(); ++it)
    {
        if((*it)->canBeDrawn())
        {
            addPickable(*it);
        }
    }
}

void CWorldBuilderView::onNodeAdded(NodeManager* sender, 
                                        ref<Node> node)
{
    if(node->canBeDrawn())
    {
        addPickable(node);
    }

    View3d::onNodeAdded(sender, node);
}

void CWorldBuilderView::onNodeRemoved(NodeManager* sender, 
                                          ref<Node> node)
{
    if(!node->canBeDrawn())
    {
        return;
    }
    
    removePickable(node);

    View3d::onNodeRemoved(sender, node);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void CWorldBuilderView::OnViewPerspective()
{
    if(getProjectionMode() == ProjectionModes::Perspective)
    {
        setProjectionMode(ProjectionModes::Orthographic);
    }
    else
    {
        setProjectionMode(ProjectionModes::Perspective);
    }
}

void CWorldBuilderView::OnUpdateViewPerspective(CCmdUI *pCmdUI)
{
    pCmdUI->SetCheck(getProjectionMode() == ProjectionModes::Perspective);
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Create Python wrappers. This code be called once the Python manager
// has been properly initialized


MPG_BEGIN_PYTHON_REGISTRATION(400)

    // Add wrapper for Node class. Node is synomym for ref<Node>
    // in the python side
    pyManager().addClass(
        "CWorldBuilderView", 
        bpy::class_<CWorldBuilderView, 
                    bpy::bases<View3d>,
                    boost::noncopyable
                    >
                ("CWorldBuilderView", bpy::no_init)
    );

MPG_END_PYTHON_REGISTRATION 
